// Copyright (c) Microsoft Corporation. All rights reserved.
using System;
using System.Reflection;
using System.Security.Permissions;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Microsoft.Tools.CodeRunner
{
    /// <summary>
    /// Environment class.
    /// </summary>
    public sealed class ScriptEnvironment : MarshalByRefObject
    {
        #region Private Fields

        private static string netFxVersion;
        private static ParsedPath complusInstallRoot;
        private static ParsedPath scriptPath;
        private static ParsedPath compilerRspPath;
        private static ParsedPath sharedRspPath;
        private static ParsedPath scriptRspPath;
        private static ParsedPath netFxInstallPath;
        private static ParsedPath netFxRefAsmPath;
        private static ParsedPath clrInstallPath;

        #endregion

        #region Constructors

        private ScriptEnvironment()
        {
        }

        static ScriptEnvironment()
        {
        }

        #endregion

        #region Class Properties

        /// <summary>
        /// Gets a directory used for Microsoft build environment support.
        /// </summary>
        public static ParsedPath ComPlusInstallRoot
        {
            get
            {
                if (complusInstallRoot == null)
                {
                    string s = Environment.GetEnvironmentVariable("COMPLUS_INSTALLROOT");

                    if (s != null)
                        complusInstallRoot = new ParsedPath(s, PathType.Directory);
                    else
                        s = String.Empty;
                }

                return complusInstallRoot;
            }
        }

        /// <summary>
        /// Gets the version of .NET Framework the script will use, either v2.0, v3.5 or v4.0.  If running
        /// on CLR v2.0 then use Fx v3.5 if installed, otherwise Fx v2.0.  If running on CLR v4.0 then use Fx 4.0.
        /// </summary>
        public static string NetFxVersion
        {
            get
            {
                if (netFxVersion == null)
                {
                    GetNetFxVersionAndPath();
                }
                    
                return netFxVersion;
            }
        }

        /// <summary>
        /// Gets a value containing the path to the .NET Framework installation.
        /// </summary>
        public static ParsedPath NetFxInstallPath
        {
            get
            {
                if (netFxInstallPath == null)
                {
                    GetNetFxVersionAndPath();
                }

                return netFxInstallPath;
            }
        }

        /// <summary>
        /// Gets the runtime install path
        /// </summary>
        public static ParsedPath ClrInstallPath
        {
            get
            {
                if (clrInstallPath == null)
                {
                    clrInstallPath = new ParsedPath(RuntimeEnvironment.GetRuntimeDirectory(), PathType.Directory);
                }

                return clrInstallPath;
            }
        }

        /// <summary>
        /// Gets a value containing the path to the .NET Framework reference assemblies or an empty path if not installed.
        /// </summary>
        public static ParsedPath NetFxReferenceAssemblyPath
        {
            get
            {
                if (netFxRefAsmPath == null)
                {
                    if (ComPlusInstallRoot != null)
                    {
                        netFxRefAsmPath = new ParsedPath(ComPlusInstallRoot, PathType.Directory);
                    }
                    else
                    {
                        string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                            @"Reference Assemblies\Microsoft\Framework\");

                        if (Directory.Exists(path))
                            netFxRefAsmPath = new ParsedPath(path, PathType.Directory);
                        else
                            netFxRefAsmPath = new ParsedPath(String.Empty);
                    }
                }

                return netFxRefAsmPath;
            }
        }
        
        /// <summary>
        /// Get the path of the currently executing script.rsp file.
        /// </summary>
        public static ParsedPath ScriptRspPath
        {
            get 
            {
                if (scriptRspPath == null)
                    scriptRspPath = new ParsedPath(scriptPath.RootDirectoryAndFile + ".rsp");
                
                return scriptRspPath;
            }
        }
        
        /// <summary>
        /// Get the path of the shared csc.rsp file.
        /// </summary>
        public static ParsedPath SharedRspPath
        {
            get 
            {
                if (sharedRspPath == null)
                    sharedRspPath = new ParsedPath(
                        new ParsedPath(Process.GetCurrentProcess().MainModule.FileName).RootAndDirectory + "csc.rsp");
                    
                return sharedRspPath;
            }
        }

        /// <summary>
        /// Get the paths of the compiler's csc.rsp file.
        /// </summary>
        public static ParsedPath CompilerRspPath
        {
            get 
            {
                if (compilerRspPath == null)
                    compilerRspPath = new ParsedPath(Path.Combine(NetFxInstallPath, "csc.rsp"));
                    
                return compilerRspPath;
            }
        }

        /// <summary>
        /// Gets the path of the currently running script if the script is being run from the runner,
        /// else the path of for main module of the currently executing process.
        /// </summary>
        /// <exception cref="System.Security.SecurityException">The path can only be set by the CSR utility.</exception>
        public static ParsedPath ScriptPath
        {
            get 
            {
                if (scriptPath == null)
                {
                    // Getting the script path right is a little complicated.  Either (a) the Scaffold or Csr tasks will set the 
                    // name of the ScriptPath directly before running the script or (b) the script is being run from the Scaffold generated
                    // VS solution, in which case we can deduce the script path from a combination of the main module name and the directory one 
                    // level up from the Scaffold temporary directory.  If (a) then we should never get to this code.

                    ParsedPath modulePath = new ParsedPath(Process.GetCurrentProcess().MainModule.FileName.ToLower());

                    // If running from either of these two processes, they will set the script path via the ScriptPath property
                    if (modulePath.FileAndExtension.CompareTo("scaffold.exe") != 0 &&
                        modulePath.FileAndExtension.CompareTo("csr.exe") != 0)
                    {
                        // So, if this is process is being run from a scaffolded project, the directory ends in something like ...\Scaffold_1234\bin\debug.  
                        // So if we go up 3 directories we should find the script.
                        if (modulePath.DirectoryDepth >= 3)
                        {
                            ParsedPath parentPath = modulePath.MakeParentPath(-3);

                            // Look for a .csr, which is most likely					
                            scriptPath = new ParsedPath(parentPath.RootAndDirectory + modulePath.File + ".csr");
                            
                            if (!File.Exists(scriptPath))
                            {
                                // OK, maybe it's a .cs file
                                scriptPath = new ParsedPath(parentPath.RootAndDirectory + modulePath.File + ".cs");
                                
                                if (!File.Exists(scriptPath))
                                {
                                    // Uh, dunno what it is.  We could be nuts here and start searching, but is it really worth it?
                                    scriptPath = null;
                                }
                            }
                        }

                        if (scriptPath == null)
                        {
                            // Worst case, we just use the module name with the .csr extension
                            scriptPath = new ParsedPath(modulePath.RootDirectoryAndFile + ".csr");
                        }
                    }
                }

                return scriptPath;
            }
            internal set 
            {
                scriptPath = value;
            }
        }

        #endregion

        #region Private Methods
        // The .NET Fx version number and install path must agree, so we figure them out at the same time
        private static void GetNetFxVersionAndPath()
        {
            // We must be running on some version of the CLR
            string clrVersion = RuntimeEnvironment.GetSystemVersion();

            clrVersion = clrVersion.Substring(0, clrVersion.LastIndexOf('.'));

            if (clrVersion == "v2.0")
            {
                // Are we running under a sandboxed CLR?
                if (ComPlusInstallRoot != null)
                {
                    // We cannot check the COMPLUS_VERSION variable is it will be set to v2.0!  In any case,
                    // upgrade to v3.5.
                    netFxVersion = "v3.5";
                    netFxInstallPath = new ParsedPath(Path.Combine(ComPlusInstallRoot, ScriptEnvironment.NetFxVersion), PathType.Directory);
                }
                else
                {
                    using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5"))
                    {
                        if (key != null)
                        {
                            string installPath = key.GetValue("InstallPath") as string;

                            if (installPath != null)
                            {
                                // Upgrade to v3.5
                                netFxVersion = "v3.5";
                                netFxInstallPath = new ParsedPath(installPath, PathType.Directory);
                            }
                        }

                        if (netFxVersion == null)
                        {
                            netFxVersion = clrVersion;
                            netFxInstallPath = new ParsedPath(RuntimeEnvironment.GetRuntimeDirectory(), PathType.Directory);
                        }
                    }
                }
            }
            else
            {
                // v4.0 is the only other supported option right now
                Debug.Assert(clrVersion == "v4.0");
                
                netFxVersion = clrVersion; 
                netFxInstallPath = new ParsedPath(RuntimeEnvironment.GetRuntimeDirectory(), PathType.Directory);
            }

            Debug.Assert(netFxVersion != null);
            Debug.Assert(netFxInstallPath != null);
        }

        #endregion
    }
}
